package com.apobates.forum.orize.servlet;

import com.apobates.forum.orize.OrizeExecutor;
import com.apobates.forum.orize.OrizeExecutorFactory;
import com.apobates.forum.orize.core.*;
import com.apobates.forum.orize.core.plug.OrizeMemberRoleAnyContainsPredicate;
import com.apobates.forum.orize.core.plug.OrizeMemberRoleContainsAllPredicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.IOException;
import java.util.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Orize过滤器
 * 适用于Servlet环境
 * 参数:
 *     loader支持:xml,json,anno
 *     path:当loader时xml/json时的资源所在的位置,需要放到WEB-INF目录下
 *     queryClass: 用户角色查询实现类全名(需要实现:OrizeMemberQuery)
 *     roleMatch: any(单角色匹配)，all(多角色匹配)
 *     ignoreDomain: 忽略的域名, 只要请求来自这些域名不进行验证. 多个之间用逗号分隔, 可选项
 *     ignorePath: 忽略的请求路径, 只要请求路径符合定义不进行验证. 多个之间用逗号分隔, 可选项
 *     ignoreExt: 忽略的请求文件扩展名, 只要请求文件符合定义不进行验证. 多个之间用逗号分隔, 可选项
 * @author xiaofanku@live.cn
 * @since 20210822
 */
public class OrizeAuthServletFilter extends OrizeAuthCommonHelper implements Filter {
    //执行器
    private OrizeExecutor executor;
    //所有初始化的参数
    Map<String,String> initParams = new HashMap<>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        List<String> allowParam = Arrays.asList("loader","path","queryClass","roleMatch","ignoreDomain","ignorePath","ignoreExt");
        Enumeration<String> names = filterConfig.getInitParameterNames();
        while(names.hasMoreElements()){
            String paramName = names.nextElement();
            if(allowParam.contains(paramName)) {
                String paramValue = filterConfig.getInitParameter(paramName);
                initParams.put(paramName, paramValue);
            }
        }
        //WEB-INF的位置
        String infPath = filterConfig.getServletContext().getRealPath("/WEB-INF/");
        //可以变更OrizeExecutor的实现
        executor = OrizeExecutorFactory.classical(getLoader(initParams.get("loader"), initParams.get("path"), infPath));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //参数
        HttpServletRequest _req = (HttpServletRequest) request;
        HttpServletResponse _res = (HttpServletResponse)response;
        //
        OrizeMemberRolePredicate memberRolePredicate = buildMemberRolePredicate(initParams.get("roleMatch"));
        OrizeAuthIgnoreConfig ignoreConfig = buildIgnoreConfig(initParams.get("ignoreDomain"), initParams.get("ignorePath"), initParams.get("ignoreExt"));
        //
        ImmutablePair<Boolean,Map> ips = authentication(executor, memberRolePredicate, _req, ignoreConfig);
        if(!ips.getLeft()){
            _req.getSession().setAttribute("__orizeLog", ips.getRight());
            _res.sendRedirect(_req.getContextPath() +"/orize/result");
            return;
        }
        chain.doFilter(request, response);
    }

    //构造用户角色匹配规划
    private OrizeMemberRolePredicate buildMemberRolePredicate(String roleMatch){
        return "any".equalsIgnoreCase(roleMatch)?new OrizeMemberRoleAnyContainsPredicate() :new OrizeMemberRoleContainsAllPredicate();
    }

    //构造请求忽略配置参数
    private OrizeAuthIgnoreConfig buildIgnoreConfig(String ignoreDomain, String ignorePath, String ignoreExt){
        OrizeAuthIgnoreConfig ignoreConfig = OrizeAuthIgnoreConfig.defaultInstance();
        final String SPLITER=",";
        if(StringUtils.isNotBlank(ignoreDomain)){
            ignoreConfig = ignoreConfig.setIgnoreDomain(ignoreDomain.split(SPLITER));
        }
        if(StringUtils.isNotBlank(ignorePath)){
            ignoreConfig = ignoreConfig.setIgnoreRequestPath(ignorePath.split(SPLITER));
        }
        if(StringUtils.isNotBlank(ignoreExt)){
            ignoreConfig = ignoreConfig.setIgnoreMediaType(ignoreExt.split(SPLITER));
        }
        return ignoreConfig;
    }

    @Override
    protected OrizeMemberQuery getMemberQuery() throws Exception {
        String _queryClass = initParams.get("queryClass");
        if(null != _queryClass){
            Class<OrizeMemberQuery> mqc = (Class<OrizeMemberQuery>) Class.forName(_queryClass).asSubclass(OrizeMemberQuery.class);
            return mqc.getDeclaredConstructor().newInstance();
        }
        return null;
    }

    @Override
    public void destroy() {
        executor.destroy();
    }


    @Override
    protected String getFilterClassName() {
        return "OrizeAuthServletFilter";
    }

}
